﻿using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Forms;
using Loki.Utils;

namespace Loki.UI.Win
{
    public abstract class Binder : LokiObject
    {
        public IValueConverter GlyphConverter { get; set; }

        public IValueConverter NameConverter { get; set; }

        protected IConductor GetContainer<TModel>(Control control, Expression<Func<TModel, object>> propertyGetter) where TModel : class
        {
            TModel model = View.GetViewModel<TModel>(control);

            if (model == null)
            {
                Log.WarnFormat("Form {0} is not binded to a {1} model", control, model);
                return null;
            }

            var bindingTarget = propertyGetter.Compile()(model) as IConductor;

            var containerModel = bindingTarget as IConductor;
            if (containerModel == null)
            {
                Log.Warn("Navigation/Menu model must be a container");
                return null;
            }
            else
            {
                return containerModel;
            }
        }

        protected void BindName(object destination, PropertyInfo destinationProperty, object source)
        {
            INotifyPropertyChanged displayNameSource = source as INotifyPropertyChanged;
            if (displayNameSource != null)
            {
                OneWay(destination, destinationProperty, displayNameSource, ExpressionHelper.GetProperty<IHaveDisplayName, string>(x => x.DisplayName), NameConverter);
                PropertySetter(destinationProperty)(destination, GetName(source));
            }
        }

        protected void OneWay(
                            object destination,
                            PropertyInfo destinationProperty,
                            INotifyPropertyChanged source,
                            PropertyInfo sourceProperty,
                            IValueConverter converter = null,
                            object converterParameter = null)
        {
            // check in control property is editable
            if (destinationProperty.CanWrite)
            {
                // get source property name
                Func<object, Action> eventFunctor = CreateValueToControlSetter(source, sourceProperty, converter, converterParameter, destinationProperty);

                // register in change manager service
                Toolkit.Events.PropertyChanged.Register(source, sourceProperty.Name, destination, (context, sender, e) => UIContext.Threading.OnUIThread(eventFunctor(context)));
            }
        }

        private Action<object, object> PropertySetter(PropertyInfo property)
        {
            var parameter = Expression.Parameter(typeof(object));
            var convertedParameter = Expression.TypeAs(parameter, property.DeclaringType);
            var value = Expression.Parameter(typeof(object));
            var convertedValue = Expression.TypeAs(value, property.PropertyType);

            var propSet = Expression.Property(convertedParameter, property);
            var assign = Expression.Assign(propSet, convertedValue);

            return Expression.Lambda<Action<object, object>>(assign, parameter, value).Compile();
        }

        private Func<object, object> PropertyGetter(PropertyInfo property)
        {
            var parameter = Expression.Parameter(typeof(object));
            var convertedParameter = Expression.TypeAs(parameter, property.DeclaringType);
            var propGet = Expression.Property(convertedParameter, property);

            return Expression.Lambda<Func<object, object>>(propGet, parameter).Compile();
        }

        private Func<object, Action> CreateValueToControlSetter(object source, PropertyInfo sourceProperty, IValueConverter converter, object converterParameter, PropertyInfo propertyToSetDescriptor)
        {
            var valueGetter = PropertyGetter(sourceProperty);
            var valueSetter = PropertySetter(propertyToSetDescriptor);

            Func<object, Action> eventFunctor = delegate(object c)
            {
                return () =>
                {
                    object value = null;
                    if (converter == null)
                    {
                        value = valueGetter(source);
                    }
                    else
                    {
                        value = converter.Convert(valueGetter(source), propertyToSetDescriptor.PropertyType, converterParameter, UIContext.Windows.Culture);
                    }

                    valueSetter(c, value);
                };
            };

            return eventFunctor;
        }

        protected string GetName(object item)
        {
            var display = item as IHaveDisplayName;
            if (display != null)
            {
                if (NameConverter == null)
                {
                    return display.DisplayName;
                }
                else
                {
                    return NameConverter.Convert(display.DisplayName, typeof(string), null, UIContext.Windows.Culture).SafeToString();
                }
            }
            else
            {
                return string.Empty;
            }
        }
    }
}